import {VNode} from "../../../type/VNode";
import {isDef} from "../../../shared/util";
import {mount} from "../../../platform/web/mount";
import {invokeLifeCycleHook} from "../lifecycle";
import {updateComponent} from "../../vdom/helpers/update-component";
import {destroyComponent} from "../../vdom/helpers/destroy-component";
import {VIRTUAL_NODE_TYPE} from "../../../constant";
import prop from "../../../platform/web/module/props";

/**
 * 组件加载与渲染流程
 * fixme 这里并不是组件实例的钩子，组件实例的钩子在 vnode.componentInstance 中
 */
const componentHooks = {
    /**
     * 这里传入 vnode 自身, 为组件创建模板内容
     * 也是在这里才开始真正渲染
     * @param {VNode} vnode
     */
    init: function (vnode) {
        // 创建组件实例
        createComponentInstanceForVNode(vnode);
    },

    /**
     * 组件创建流程 prepatch 回调
     * @param {VNode} vnode
     */
    prepatch: function (vnode) {
        // fixme do nothing
    },

    // fixme 为什么这里没有 beforeCreate, created
    // fixme 因为 beforeCreate, created 应放在 _init 函数内

    mount: function (vnode) {
        const componentInstance = vnode.componentInstance;
        // 挂载组件模板内容
        // 占位节点在 patch 过程中，因为 isSameNode 返回为 false
        // 所以占位节点在 patch 过程中被卸载
        mount(componentInstance);
        // 挂载完后, 更新占位节点的 elem
        vnode.elem = componentInstance.preVNode.elem;
    },

    /**
     * 组件模板内容更新 update 回调
     * 因为是 update 回调，所以可以认为参数都是 VNode
     * @param {VNode} oldNode
     * @param {VNode} newNode
     */
    update: function (oldNode, newNode) {
        // 两者是同一个组件实例的不同渲染，所以 oldNode 里的 componentInstance 可以被直接复用
        // do patch (virtual dom diff)
        updateComponent(oldNode, newNode);
    },

    /**
     * 组件模板内容销毁 destroy 回调
     * @param {VNode} vnode
     */
    destroy: function (vnode) {
        // 获取组件实例
        const componentInstance = vnode.componentInstance;
        // 调用组件生命周期钩子 beforeDestroy
        invokeLifeCycleHook(componentInstance, 'beforeDestroy');
        // destroy instance
        destroyComponent(vnode);
        // 调用组件生命周期钩子 destroyed
        invokeLifeCycleHook(componentInstance, 'destroyed');
    },
};

/**
 * 根据组件的占位符 VNode 节点中的组件配置信息
 * 渲染出实际的组件模板内容
 * @param {VNode} vnode 组件占位节点
 */
function createComponentInstanceForVNode (vnode) {
    // 生成组件实例配置
    const options = {
        tag: vnode.componentOptions.tag,
        isComponent: true,
        placeholder: vnode,
        parent: vnode.parent.elem,
        props: vnode.componentOptions.props,
        listeners: vnode.componentOptions.listeners,
        slots: vnode.componentOptions.slots,
        parentComponent: vnode.componentOptions.parentComponent
    };
    // 生成组件实例
    vnode.componentInstance = new vnode.componentOptions.Ctor(options);
}

/**
 * 这里与 Vue 不太一样的地方, Vue 貌似在 data 里安装了 Hook，
 *  所以这里需要处理与已安装的 Hook 冲突
 * 但是因为我没有在 codegen 生成相应的 Hook, 所以这里我只直接赋值就可以了
 *
 * 为组件占位符节点, 安装组件 Hook
 * 这里安装的 hook, 我设计成组件挂载的一个流程
 * 而组件自身的 hook， 我将其放在了 vnode.componentInstance 上
 * 这样逻辑就更清晰了
 * @param {Object} data
 */
function installComponentHook (data) {
    const hooks = Object.keys(componentHooks);
    for (let i=0; i<hooks.length; i++) {
        const hookName = hooks[i];
        data[hookName] = componentHooks[hookName];
    }
}

/**
 * 获取组件 prop 属性
 * @param {VNode} vnode
 * @param {Reactive} context
 * @param {Function} Ctor
 */
function getComponentProps (vnode, context, Ctor) {
    const props = {};
    // 获取组件定义的 props 属性列表
    const constructor = Ctor.prototype.constructor;
    const propList = constructor.options.props || [];
    // 向其中添加一些节点固有属性 style, class, id
    const add_properties = ['class', 'style', 'id'];
    propList.push(...add_properties);
    // 从 vnode.data 中的 staticAttrMap, dynamicAttrMap 中查找 propList 包含的属性的值
    const staticAttrs = vnode.data.attrs;
    const dynamicAttrs = vnode.data.bindAttrs || {};
    for (let i=0; i<propList.length; i++) {
        const property = propList[i];
        const dynamicValue = dynamicAttrs[property];
        const staticValue = staticAttrs[property];
        isDef(dynamicValue)
            ? props[property] = dynamicValue
            : isDef(staticValue)
                ? props[property] = staticValue
                : undefined; // 如果没有该属性的值，相当于没有定义
    }
    // 返回创建好的 props 对象
    return props;
}

/**
 * 获取组件绑定 listener 属性
 * @param {VNode} vnode
 * @return {{eventName: Array<Function>}}
 */
function getComponentListeners (vnode) {
    return vnode.data.bindOn;
}

/**
 * 创建组件节点， 只是创建一个虚拟 VNode 节点，并不是真的创建组件的内容
 * @param {Function} Ctor
 * @param {Reactive} context
 * @param {String} tag
 * @param {Object} data
 * @param {Array<VNode|Array>} children
 * @param {Object} options
 * @return {VNode}
 */
export function createComponent (Ctor, context, tag, data, children, options) {
    // 创建一个 vnode 节点 (实际是组件模板内容的占位符)
    const vnode = new VNode(
        `Reactive-Component-${tag}-${isDef(Ctor.cid) ? Ctor.cid : ''}`,
        data, [], undefined);
    vnode.context = context;
    vnode.nodeType = VIRTUAL_NODE_TYPE.COMMENT;

    // 组件节点配置
    // 获取 props 属性
    const props = getComponentProps(vnode, context, Ctor);
    // 获取 listeners 属性
    const listeners = getComponentListeners(vnode);
    // 组件实例配置
    vnode.isComponent = true;
    vnode.componentOptions = {
        Ctor: Ctor,
        tag: tag,
        props: props,
        listeners: listeners,
        slots: children,
        parentComponent: context,
    };
    // 为 vnode 节点的 data 安装生命周期函数
    installComponentHook(data);
    // 返回创建的 vnode 节点
    return vnode;
}


